package cool.taomu.toolkit.service.centre.kademlia.utils

import com.google.gson.Gson
import com.google.inject.Inject
import cool.taomu.toolkit.crypto.Base64
import cool.taomu.toolkit.service.kademlia.entity.Constant
import cool.taomu.toolkit.service.kademlia.entity.KPeerNode
import cool.taomu.toolkit.service.kademlia.service.ICheckPeer
import cool.taomu.toolkit.service.kademlia.service.KRoutingTable
import cool.taomu.toolkit.service.kademlia.thrift.KademliaService.Client
import cool.taomu.toolkit.service.kademlia.utils.BEncodeUtils
import cool.taomu.toolkit.service.kademlia.utils.FileDownloadUtils
import cool.taomu.toolkit.utils.ByteBufferUtils
import java.io.File
import java.io.FileOutputStream
import java.nio.charset.Charset
import java.security.MessageDigest
import java.util.ArrayList
import java.util.Arrays
import java.util.Map
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.locks.ReentrantLock
import org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory

class PeerConfigureFileDownload {

    val static LOG = LoggerFactory.getLogger(PeerConfigureFileDownload);

    @Inject
    KRoutingTable router

    @Inject
    ServiceCentreClientPool clientPool;

    @Inject
    ICheckPeer checkPeer;

    val Map<String, ReentrantLock> downloadLocks = new ConcurrentHashMap();
    val ConcurrentHashMap<String, Boolean> downloadedFiles = new ConcurrentHashMap();

    private def downloadFile(Client client, String filename, ArrayList<byte[]> piecesHash) {
        var boolean success
        var fdl = new FileDownloadUtils()
        var dfilePath = #[Constant.TORRENT_DOWNLOAD_PATH, filename].join(File.separator)
        try(var out2 = new FileOutputStream(dfilePath)) {
            success = fdl.download(filename, out2, [ name |
                var respSize = client.getFileSize(name)
                if (respSize.status == 200) {
                    var data = ByteBufferUtils.toString(respSize.data, Charset.forName("UTF-8"))
                    return Long.parseLong(data)
                }
                return 0L;
            ], [
                var response = client.downloadFile(it.infoHash, it.offset, it.readLength)
                if (response.status == 200) {
                    var buffer = response.getData()
                    var sha1 = MessageDigest.getInstance("SHA-1");
                    var byte[] sha1Hash = sha1.digest(buffer);
                    var byte[] piecesHashByte = piecesHash.get(it.piecesHashIndex);
                    if (Arrays.equals(sha1Hash, piecesHashByte)) {
                        return buffer
                    } else {
                        LOG.info("sha1对比 第{}块数据 :\n{}\n{}", it.piecesHashIndex, BEncodeUtils.bytesToHex(sha1Hash),
                            (BEncodeUtils.bytesToHex(piecesHashByte)))
                        LOG.info("文件块损害")
                    }
                }
                return newByteArrayOfSize(0);
            ])
        }
        if (success) {
            var jsonConfig = FileUtils.readFileToString(new File(dfilePath), "UTF-8")
            LOG.info("配置文件下载完成，内容：\n{}", jsonConfig)
            var nodeConfig = new Gson().fromJson(jsonConfig, KPeerNode)
            LOG.info("将下载到的节点信息添加到K桶中")
            this.router.addPeerNode(nodeConfig, checkPeer);
            return true;
        }
        return false
    }

    /**
     * 判断文件是否已经下载
     * 
     * @param fileUrl 文件地址
     * @return 是否已下载
     */
    private def boolean isDownloaded(String fileUrl) {
        return downloadedFiles.containsKey(fileUrl);
    }

    /**
     * 标记文件为已下载
     * 
     * @param fileUrl 文件地址
     */
    private def void markAsDownloaded(String fileUrl) {
        downloadedFiles.put(fileUrl, Boolean.TRUE);
    }

    def boolean downlaodTorrent(KPeerNode peer) {
        val filename = #[peer.host, peer.port, "local", "json", "torrent"].join(".");
        val lock = downloadLocks.computeIfAbsent(filename, [new ReentrantLock()]);
        lock.lock();
        try {
            if (isDownloaded(filename)) {
                LOG.debug(Thread.currentThread().getName() + " 文件已存在，跳过下载: " + filename);
                return true;
            }
            var opl = clientPool.peerClient(peer, router.localNode, [ client |
                if (client !== null) {
                    LOG.info("需要下载的文件:{}", filename)
                    // var node = new Node(router.localNode.host, router.localNode.port)
                    var resp = client.getFileLastModifiedTime(filename);
                    if (resp.status == 200) {
                        // 文件保存路径
                        var filePath = #[Constant.TORRENT_DOWNLOAD_PATH, filename].join(File.separator)
                        var file = new File(filePath)
                        var localFileDate = 0L;
                        // 判断下载文件是否存在
                        if (file.exists) {
                            LOG.info("需要下载的文件存在", filename)
                            var hash = BEncodeUtils.read(file.absolutePath)
                            // 文件存在就获取文件中记录的creation date时间
                            localFileDate = hash.get("creation date") as Long
                        }
                        var remoteCreationDate = ByteBufferUtils.toString(resp.data, Charset.forName("UTF-8"))
                        // 如果文件不存在 或者 本地文件中的creation date 小于 从服务器获取的 就开始下载
                        if (!file.exists || localFileDate < Long.parseLong(remoteCreationDate)) {
                            LOG.info("文件修改时间 L:{},R:{}", localFileDate, remoteCreationDate)
                            LOG.info("本地文件修改时间 小于 服务器上的文件修改时间，开始下载新文件")
                            var fdl = new FileDownloadUtils()
                            var success = false;
                            try(var out = new FileOutputStream(file)) {
                                success = fdl.download(filename, out, [ name |
                                    var respSize = client.getFileSize(name)
                                    if (respSize.status == 200) {
                                        var data = ByteBufferUtils.toString(respSize.data, Charset.forName("UTF-8"))
                                        return Long.parseLong(data)
                                    }
                                    return 0L;
                                ], [
                                    var response = client.downloadFile(it.infoHash, it.offset, it.readLength)
                                    if (response.status == 200) {
                                        return response.getData()
                                    }
                                    return newByteArrayOfSize(0);
                                ])
                            }
                            // succes 为 true 就为文件下载成功
                            if (success) {
                                // 标记为已下载
                                markAsDownloaded(filename);
                                LOG.info("文件 {} 下载成功", filename)
                                success = false;
                                // 获取torrent中的文件信息，用来下载文件
                                var hash2 = BEncodeUtils.read(file.absolutePath)
                                var infoHash = hash2.get("info") as Map<String, Object>
                                var piecesBase64 = infoHash.get("pieces") as String
                                var pieces = new Base64(piecesBase64.bytes).decode();
                                try {
                                    val piecesHash = BEncodeUtils.getPieceHashes(pieces)
                                    LOG.info("文件 {} 的 piecesHash size \n {}", filename, piecesHash.size)
                                    if (infoHash.containsKey("name")) {
                                        var dfname = infoHash.get("name") as String
                                        LOG.info("需要下載的文件 {}", dfname)
                                        return this.downloadFile(client, dfname, piecesHash)
                                    } else {
                                        LOG.info("为找到 文件 {} 的 infoHash \n {}", filename, infoHash)
                                    }
                                } catch (IllegalArgumentException ex) {
                                    LOG.error(ex.message)
                                }
                            }
                        }
                    }
                }
                return false
            ])
            if (opl.present) {
                return opl.get()
            }
        } finally {
            lock.unlock();
            downloadedFiles.remove(filename);
        }
        return false;
    }
}
